home *** CD-ROM | disk | FTP | other *** search
/ 130 MIDI Tool Box / 130 MIDI Tool Box.iso / drum / drum.c < prev    next >
C/C++ Source or Header  |  1992-02-13  |  20KB  |  564 lines

  1. /*----------------------------------------------------
  2.    DRUM.C -- MIDI Drum Machine for Multimedia Windows
  3.              (c) Charles Petzold, 1992
  4.   ----------------------------------------------------*/
  5.  
  6. #include <windows.h>
  7. #include <stdlib.h>
  8. #include <string.h>
  9. #include <math.h>
  10. #include "drum.h"
  11. #include "drumdll.h"
  12. #include "drumfile.h"
  13.  
  14. typedef unsigned int UINT ;
  15.  
  16. #define min(a,b) (((a) < (b)) ? (a) : (b))
  17. #define max(a,b) (((a) > (b)) ? (a) : (b))
  18.  
  19. long FAR PASCAL _export WndProc   (HWND, UINT, UINT, LONG) ;
  20. BOOL FAR PASCAL _export AboutProc (HWND, UINT, UINT, LONG) ;
  21.  
  22. void  DrawRectangle (HDC, int, int, DWORD *, DWORD *) ;
  23. void  ErrorMessage  (HWND, char *, LPSTR) ;
  24. void  DoCaption     (HWND, char *) ;
  25. short AskAboutSave  (HWND, char *) ;
  26.  
  27. char *szPerc [NUM_PERC] =
  28.      {
  29.      "Acoustic Bass Drum", "Bass Drum 1",     "Side Stick",
  30.      "Acoustic Snare",     "Hand Clap",       "Electric Snare",
  31.      "Low Floor Tom",      "Closed High-Hat", "High Floor Tom",
  32.      "Pedal High Hat",     "Low Tom",         "Open High Hat",
  33.      "Low-Mid Tom",        "High-Mid Tom",    "Crash Cymbal 1",
  34.      "High Tom",           "Ride Cymbal 1",   "Chinese Cymbal",
  35.      "Ride Bell",          "Tambourine",      "Splash Cymbal",
  36.      "Cowbell",            "Crash Cymbal 2",  "Vibraslap",
  37.      "Ride Cymbal 2",      "High Bongo",      "Low Bongo",
  38.      "Mute High Conga",    "Open High Conga", "Low Conga",
  39.      "High Timbale",       "Low Timbale",     "High Agogo",
  40.      "Low Agogo",          "Cabasa",          "Maracas",
  41.      "Short Whistle",      "Long Whistle",    "Short Guiro",
  42.      "Long Guiro",         "Claves",          "High Wood Block",
  43.      "Low Wood Block",     "Mute Cuica",      "Open Cuica",
  44.      "Mute Triangle",      "Open Triangle"
  45.      } ;
  46.  
  47. char   szAppName  []  = "Drum" ;
  48. char   szUntitled []  = "(Untitled)" ;
  49. char   szBuffer [80 + _MAX_FNAME + _MAX_EXT] ;
  50. HANDLE hInst ;
  51. short  cxChar, cyChar ;
  52.  
  53. int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
  54.                     LPSTR lpszCmdParam, int nCmdShow)
  55.      {
  56.      HWND        hwnd ;
  57.      MSG         msg ;
  58.      WNDCLASS    wndclass ;
  59.  
  60.      if (hPrevInstance)
  61.           {
  62.           ErrorMessage (NULL, "Only one instance is allowed!", NULL) ;
  63.           return 0 ;
  64.           }
  65.  
  66.      hInst = hInstance ;
  67.  
  68.      wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
  69.      wndclass.lpfnWndProc   = WndProc ;
  70.      wndclass.cbClsExtra    = 0 ;
  71.      wndclass.cbWndExtra    = 0 ;
  72.      wndclass.hInstance     = hInstance ;
  73.      wndclass.hIcon         = LoadIcon (hInstance, szAppName) ;
  74.      wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
  75.      wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
  76.      wndclass.lpszMenuName  = szAppName ;
  77.      wndclass.lpszClassName = szAppName ;
  78.  
  79.      RegisterClass (&wndclass) ;
  80.  
  81.      hwnd = CreateWindow (szAppName, NULL,
  82.                           WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL,
  83.                           CW_USEDEFAULT, CW_USEDEFAULT,
  84.                           CW_USEDEFAULT, CW_USEDEFAULT,
  85.                           NULL, NULL, hInstance, lpszCmdParam) ;
  86.  
  87.      ShowWindow (hwnd, SW_SHOWMAXIMIZED) ;
  88.      UpdateWindow (hwnd) ;
  89.  
  90.      while (GetMessage (&msg, NULL, 0, 0))
  91.           {
  92.           TranslateMessage (&msg) ;
  93.           DispatchMessage (&msg) ;
  94.           }
  95.      return msg.wParam ;
  96.      }
  97.  
  98. long FAR PASCAL _export WndProc (HWND hwnd, UINT message, UINT wParam,
  99.                                                           LONG lParam)
  100.      {
  101.      static BOOL    bNeedSave ;
  102.      static char    szFileName  [_MAX_PATH],
  103.                     szTitleName [_MAX_FNAME + _MAX_EXT] ;
  104.      static DRUM    drum ;
  105.      static FARPROC lpfnAboutProc ;
  106.      static HMENU   hMenu ;
  107.      static int     iTempo = 50, iIndexLast ;
  108.      char *         szError ;
  109.      DWORD          dwCurrPos ;
  110.      HDC            hdc ;
  111.      int            i, x, y ;
  112.      PAINTSTRUCT    ps ;
  113.  
  114.      switch (message)
  115.           {
  116.           case WM_CREATE:
  117.                          // Initialize DRUM structure
  118.  
  119.                drum.iMsecPerBeat = 100 ;
  120.                drum.iVelocity    =  64 ;
  121.                drum.iNumBeats    =  32 ;
  122.  
  123.                DrumSetParams (&drum) ;
  124.  
  125.                          // Other initialization
  126.  
  127.                cxChar = LOWORD (GetDialogBaseUnits ()) ;
  128.                cyChar = HIWORD (GetDialogBaseUnits ()) ;
  129.  
  130.                lpfnAboutProc = MakeProcInstance ((FARPROC) AboutProc, hInst) ;
  131.                hMenu = GetMenu (hwnd) ;
  132.  
  133.                          // Process command line and start sequence
  134.  
  135.                lstrcpy (szFileName,
  136.                         ((LPCREATESTRUCT) lParam)->lpCreateParams) ;
  137.  
  138.                if (lstrlen (szFileName))
  139.                     {
  140.                     if (DrumFileParse (szFileName, szTitleName))
  141.                          {
  142.                          szError = DrumFileRead (&drum, szFileName) ;
  143.  
  144.                          if (szError == NULL)
  145.                               {
  146.                               iTempo = (int) (50 *
  147.                                              (log10 (drum.iMsecPerBeat) - 1)) ;
  148.  
  149.                               DrumSetParams (&drum) ;
  150.                               }
  151.                          else
  152.                               {
  153.                               ErrorMessage (hwnd, szError, szTitleName) ;
  154.                               szTitleName [0] = '\0' ;
  155.                               }
  156.                          }
  157.                     else
  158.                          {
  159.                          ErrorMessage (hwnd, "Invalid command line: %s",
  160.                                    ((LPCREATESTRUCT) lParam)->lpCreateParams) ;
  161.                          szTitleName [0] = '\0' ;
  162.                          }
  163.                     }
  164.  
  165.                          // Initialize "Volume" scroll bar
  166.  
  167.                SetScrollRange (hwnd, SB_HORZ, 1, 127, FALSE) ;
  168.                SetScrollPos   (hwnd, SB_HORZ, drum.iVelocity, TRUE) ;
  169.  
  170.                          // Initialize "Tempo" scroll bar
  171.  
  172.                SetScrollRange (hwnd, SB_VERT, 0, 100, FALSE) ;
  173.                SetScrollPos   (hwnd, SB_VERT, iTempo, TRUE) ;
  174.  
  175.                DoCaption (hwnd, szTitleName) ;
  176.                return 0 ;
  177.  
  178.           case WM_COMMAND:
  179.                switch (wParam)
  180.                     {
  181.                     case IDM_NEW:
  182.                          if (bNeedSave && IDCANCEL ==
  183.                                    AskAboutSave (hwnd, szTitleName))
  184.                               return 0 ;
  185.  
  186.                                    // Clear drum pattern
  187.  
  188.                          for (i = 0 ; i < NUM_PERC ; i++)
  189.                               {
  190.                               drum.dwSeqBas [i] = 0L ;
  191.                               drum.dwSeqExt [i] = 0L ;
  192.                               }
  193.  
  194.                          InvalidateRect (hwnd, NULL, FALSE) ;
  195.                          DrumSetParams (&drum) ;
  196.                          bNeedSave = FALSE ;
  197.                          return 0 ;
  198.  
  199.                     case IDM_OPEN:
  200.                                    // Save previous file
  201.  
  202.                          if (bNeedSave && IDCANCEL ==
  203.                                    AskAboutSave (hwnd, szTitleName))
  204.                               return 0 ;
  205.  
  206.                                    // Open the selected file
  207.  
  208.                          if (DrumFileOpenDlg (hwnd, szFileName, szTitleName))
  209.                               {
  210.                               szError = DrumFileRead (&drum, szFileName) ;
  211.  
  212.                               if (szError != NULL)
  213.                                    {
  214.                                    ErrorMessage (hwnd, szError, szTitleName) ;
  215.                                    szTitleName [0] = '\0' ;
  216.                                    }
  217.                               else
  218.                                    {
  219.                                              // Set new parameters
  220.  
  221.                                    iTempo = (int) (50 *
  222.                                              (log10 (drum.iMsecPerBeat) - 1)) ;
  223.  
  224.                                    SetScrollPos (hwnd, SB_VERT, iTempo, TRUE) ;
  225.                                    SetScrollPos (hwnd, SB_HORZ,
  226.                                                  drum.iVelocity, TRUE) ;
  227.  
  228.                                    DrumSetParams (&drum) ;
  229.                                    InvalidateRect (hwnd, NULL, FALSE) ;
  230.                                    bNeedSave = FALSE ;
  231.                                    }
  232.  
  233.                               DoCaption (hwnd, szTitleName) ;
  234.                               }
  235.                          return 0 ;
  236.  
  237.                     case IDM_SAVE:
  238.                     case IDM_SAVEAS:
  239.                                    // Save the selected file
  240.  
  241.                          if ((wParam == IDM_SAVE && szTitleName [0]) ||
  242.                               DrumFileSaveDlg (hwnd, szFileName, szTitleName))
  243.                               {
  244.                               szError = DrumFileWrite (&drum, szFileName) ;
  245.  
  246.                               if (szError != NULL)
  247.                                    {
  248.                                    ErrorMessage (hwnd, szError, szTitleName) ;
  249.                                    szTitleName [0] = '\0' ;
  250.                                    }
  251.                               else
  252.                                    bNeedSave = FALSE ;
  253.  
  254.                               DoCaption (hwnd, szTitleName) ;
  255.                               }
  256.                          return 0 ;
  257.  
  258.                     case IDM_EXIT:
  259.                          SendMessage (hwnd, WM_SYSCOMMAND, SC_CLOSE, 0L) ;
  260.                          return 0 ;
  261.  
  262.                     case IDM_RUNNING:
  263.                                         // Begin sequence
  264.  
  265.                          if (!DrumBeginSequence (hwnd))
  266.                               {
  267.                               ErrorMessage (hwnd,
  268.                                         "Could not start MIDI sequence -- "
  269.                                         "MIDI Mapper device is unavailable!",
  270.                                         szTitleName) ;
  271.                               }
  272.                          else
  273.                               {
  274.                               CheckMenuItem (hMenu, IDM_RUNNING,   MF_CHECKED);
  275.                               CheckMenuItem (hMenu, IDM_STOPPED, MF_UNCHECKED);
  276.                               }
  277.                          return 0 ;
  278.  
  279.                     case IDM_STOPPED:
  280.                                         // Finish at end of sequence
  281.  
  282.                          DrumEndSequence (FALSE) ;
  283.                          return 0 ;
  284.  
  285.                     case IDM_ABOUT:
  286.                          DialogBox (hInst, "AboutBox", hwnd, lpfnAboutProc) ;
  287.                          return 0 ;
  288.                     }
  289.                return 0 ;
  290.  
  291.           case WM_LBUTTONDOWN:
  292.           case WM_RBUTTONDOWN:
  293.                hdc = GetDC (hwnd) ;
  294.  
  295.                          // Convert mouse coordinates to grid coordinates
  296.  
  297.                x =     LOWORD (lParam) / cxChar - 40 ;
  298.                y = 2 * HIWORD (lParam) / cyChar -  2 ;
  299.  
  300.                          // Set a new number of beats of sequence
  301.  
  302.                if (x > 0 && x <= 32 && y < 0)
  303.                     {
  304.                     SetTextColor (hdc, RGB (255, 255, 255)) ;
  305.                     TextOut (hdc, (40 + drum.iNumBeats) * cxChar, 0, ":|", 2);
  306.                     SetTextColor (hdc, RGB (0, 0, 0)) ;
  307.  
  308.                     if (drum.iNumBeats % 4 == 0)
  309.                          TextOut (hdc, (40 + drum.iNumBeats) * cxChar, 0,
  310.                                        ".", 1) ;
  311.  
  312.                     drum.iNumBeats = x ;
  313.  
  314.                     TextOut (hdc, (40 + drum.iNumBeats) * cxChar, 0, ":|", 2) ;
  315.  
  316.                     bNeedSave = TRUE ;
  317.                     }
  318.  
  319.                          // Set or reset a percussion instrument beat
  320.  
  321.                if (x >= 0 && x < 32 && y >= 0 && y < NUM_PERC)
  322.                     {
  323.                     if (message == WM_LBUTTONDOWN)
  324.                          drum.dwSeqBas[y] ^= (1L << x) ;
  325.                     else
  326.                          drum.dwSeqExt[y] ^= (1L << x) ;
  327.  
  328.                     DrawRectangle (hdc, x, y, drum.dwSeqBas, drum.dwSeqExt) ;
  329.  
  330.                     bNeedSave = TRUE ;
  331.                     }
  332.  
  333.                ReleaseDC (hwnd, hdc) ;
  334.                DrumSetParams (&drum) ;
  335.                return 0 ;
  336.  
  337.           case WM_HSCROLL:
  338.                               // Change the note velocity
  339.  
  340.                switch (wParam)
  341.                     {
  342.                     case SB_LINEUP:         drum.iVelocity -= 1 ;  break ;
  343.                     case SB_LINEDOWN:       drum.iVelocity += 1 ;  break ;
  344.                     case SB_PAGEUP:         drum.iVelocity -= 8 ;  break ;
  345.                     case SB_PAGEDOWN:       drum.iVelocity += 8 ;  break ;
  346.                     case SB_THUMBPOSITION:
  347.                          drum.iVelocity = LOWORD (lParam) ;
  348.                          break ;
  349.  
  350.                     default:
  351.                          return 0 ;
  352.                     }
  353.  
  354.                drum.iVelocity = max (1, min (drum.iVelocity, 127)) ;
  355.                SetScrollPos (hwnd, SB_HORZ, drum.iVelocity, TRUE) ;
  356.                DrumSetParams (&drum) ;
  357.                bNeedSave = TRUE ;
  358.                return 0 ;
  359.  
  360.           case WM_VSCROLL:
  361.                               // Change the tempo
  362.  
  363.                switch (wParam)
  364.                     {
  365.                     case SB_LINEUP:         iTempo -=  1 ;  break ;
  366.                     case SB_LINEDOWN:       iTempo +=  1 ;  break ;
  367.                     case SB_PAGEUP:         iTempo -= 10 ;  break ;
  368.                     case SB_PAGEDOWN:       iTempo += 10 ;  break ;
  369.                     case SB_THUMBPOSITION:
  370.                          iTempo = LOWORD (lParam) ;
  371.                          break ;
  372.  
  373.                     default:
  374.                          return 0 ;
  375.                     }
  376.  
  377.                iTempo = max (0, min (iTempo, 100)) ;
  378.                SetScrollPos (hwnd, SB_VERT, iTempo, TRUE) ;
  379.  
  380.                drum.iMsecPerBeat = (WORD) (10 * pow (100, iTempo / 100.0)) ;
  381.  
  382.                DrumSetParams (&drum) ;
  383.                bNeedSave = TRUE ;
  384.                return 0 ;
  385.  
  386.           case WM_PAINT:
  387.                hdc = BeginPaint (hwnd, &ps) ;
  388.  
  389.                SetTextAlign (hdc, TA_UPDATECP) ;
  390.                SetBkMode (hdc, TRANSPARENT) ;
  391.  
  392.                          // Draw the text strings and horizontal lines
  393.  
  394.                for (i = 0 ; i < NUM_PERC ; i++)
  395.                     {
  396.                     MoveTo (hdc, i & 1 ? 20 * cxChar : cxChar,
  397.                                  (2 * i + 3) * cyChar / 4) ;
  398.  
  399.                     TextOut (hdc, 0, 0, szPerc [i], strlen (szPerc [i])) ;
  400.  
  401.                     dwCurrPos = GetCurrentPosition (hdc) ;
  402.  
  403.                     x = LOWORD (dwCurrPos) ;
  404.                     y = HIWORD (dwCurrPos) ;
  405.  
  406.                     MoveTo (hdc,  x + cxChar, y + cyChar / 2) ;
  407.                     LineTo (hdc, 39 * cxChar, y + cyChar / 2) ;
  408.                     }
  409.  
  410.                SetTextAlign (hdc, 0) ;
  411.  
  412.                          // Draw rectangular grid, repeat mark, and beat marks
  413.  
  414.                for (x = 0 ; x < 32 ; x++)
  415.                     {
  416.                     for (y = 0 ; y < NUM_PERC ; y++)
  417.                          DrawRectangle (hdc, x, y, drum.dwSeqBas,
  418.                                                    drum.dwSeqExt) ;
  419.  
  420.                     SetTextColor (hdc, x == drum.iNumBeats - 1 ?
  421.                                        RGB (0, 0, 0) : RGB (255, 255, 255)) ;
  422.  
  423.                     TextOut (hdc, (41 + x) * cxChar, 0, ":|", 2) ;
  424.  
  425.                     SetTextColor (hdc, RGB (0, 0, 0)) ;
  426.  
  427.                     if (x % 4 == 0)
  428.                          TextOut (hdc, (40 + x) * cxChar, 0, ".", 1) ;
  429.                     }
  430.  
  431.                EndPaint (hwnd, &ps) ;
  432.                return 0 ;
  433.  
  434.           case WM_USER_NOTIFY:
  435.                               // Draw the "bouncing ball"
  436.  
  437.                hdc = GetDC (hwnd) ;
  438.  
  439.                SelectObject (hdc, GetStockObject (NULL_PEN)) ;
  440.                SelectObject (hdc, GetStockObject (WHITE_BRUSH)) ;
  441.  
  442.                for (i = 0 ; i < 2 ; i++)
  443.                     {
  444.                     x = iIndexLast ;
  445.                     y = NUM_PERC + 1 ;
  446.  
  447.                     Ellipse (hdc, (x + 40) * cxChar, (2 * y + 3) * cyChar / 4,
  448.                                   (x + 41) * cxChar, (2 * y + 5) * cyChar / 4);
  449.  
  450.                     iIndexLast = wParam ;
  451.                     SelectObject (hdc, GetStockObject (BLACK_BRUSH)) ;
  452.                     }
  453.  
  454.                ReleaseDC (hwnd, hdc) ;
  455.                return 0 ;
  456.  
  457.           case WM_USER_ERROR:
  458.                ErrorMessage (hwnd, "Can't set timer event for tempo",
  459.                                    szTitleName) ;
  460.  
  461.                                                   // fall through
  462.           case WM_USER_FINISHED:
  463.                DrumEndSequence (TRUE) ;
  464.                CheckMenuItem (hMenu, IDM_RUNNING,   MF_UNCHECKED) ;
  465.                CheckMenuItem (hMenu, IDM_STOPPED, MF_CHECKED) ;
  466.                return 0 ;
  467.  
  468.           case WM_CLOSE:
  469.                if (!bNeedSave || IDCANCEL != AskAboutSave (hwnd, szTitleName))
  470.                     DestroyWindow (hwnd) ;
  471.  
  472.                return 0 ;
  473.  
  474.           case WM_QUERYENDSESSION:
  475.                if (!bNeedSave || IDCANCEL != AskAboutSave (hwnd, szTitleName))
  476.                     return 1L ;
  477.  
  478.                return 0 ;
  479.  
  480.           case WM_DESTROY:
  481.                DrumEndSequence (TRUE) ;
  482.                PostQuitMessage (0) ;
  483.                return 0 ;
  484.           }
  485.  
  486.      return DefWindowProc (hwnd, message, wParam, lParam) ;
  487.      }
  488.  
  489. BOOL FAR PASCAL _export AboutProc (HWND hDlg, UINT message, UINT wParam,
  490.                                                             LONG lParam)
  491.      {
  492.      switch (message)
  493.           {
  494.           case WM_INITDIALOG:
  495.                return TRUE ;
  496.  
  497.           case WM_COMMAND:
  498.                switch (wParam)
  499.                     {
  500.                     case IDOK:
  501.                          EndDialog (hDlg, 0) ;
  502.                          return TRUE ;
  503.                     }
  504.                break ;
  505.           }
  506.      return FALSE ;
  507.      }
  508.  
  509. void DrawRectangle (HDC hdc, int x, int y, DWORD *dwSeqBas, DWORD *dwSeqExt)
  510.      {
  511.      int iBrush ;
  512.  
  513.      if (dwSeqBas [y] & dwSeqExt [y] & (1L << x))
  514.           iBrush = BLACK_BRUSH ;
  515.  
  516.      else if (dwSeqBas [y] & (1L << x))
  517.           iBrush = LTGRAY_BRUSH ;
  518.  
  519.      else if (dwSeqExt [y] & (1L << x))
  520.           iBrush = DKGRAY_BRUSH ;
  521.  
  522.      else
  523.           iBrush = WHITE_BRUSH ;
  524.  
  525.      SelectObject (hdc, GetStockObject (iBrush)) ;
  526.  
  527.      Rectangle (hdc, (x + 40) * cxChar    , (2 * y + 4) * cyChar / 4,
  528.                      (x + 41) * cxChar + 1, (2 * y + 6) * cyChar / 4 + 1) ;
  529.      }
  530.  
  531. void ErrorMessage (HWND hwnd, char * szError, LPSTR szTitleName)
  532.      {
  533.      wsprintf (szBuffer, szError,
  534.                     (LPSTR) (szTitleName [0] ? szTitleName : szUntitled)) ;
  535.  
  536.      MessageBeep (MB_ICONEXCLAMATION) ;
  537.      MessageBox (hwnd, szBuffer, szAppName, MB_OK | MB_ICONEXCLAMATION) ;
  538.      }
  539.  
  540. void DoCaption (HWND hwnd, char * szTitleName)
  541.      {
  542.      wsprintf (szBuffer, "MIDI Drum Machine - %s",
  543.                (LPSTR) (szTitleName [0] ? szTitleName : szUntitled)) ;
  544.  
  545.      SetWindowText (hwnd, szBuffer) ;
  546.      }
  547.  
  548. short AskAboutSave (HWND hwnd, char * szTitleName)
  549.      {
  550.      short nReturn ;
  551.  
  552.      wsprintf (szBuffer, "Save current changes in %s?",
  553.                (LPSTR) (szTitleName [0] ? szTitleName : szUntitled)) ;
  554.  
  555.      nReturn = MessageBox (hwnd, szBuffer, szAppName,
  556.                            MB_YESNOCANCEL | MB_ICONQUESTION) ;
  557.  
  558.      if (nReturn == IDYES)
  559.           if (!SendMessage (hwnd, WM_COMMAND, IDM_SAVE, 0L))
  560.                nReturn = IDCANCEL ;
  561.  
  562.      return nReturn ;
  563.      }
  564.